/**
 * 							USER MEMEORY HEAP MANAGEMENT
 * @brief	  :User memory heap.
 * @author    :chy.
 * @note	  :this file manage the virtual memory heap of user
 * @comments  :manage virtual memory of user
 * @warning	  :=>We always use virtual memory here!
 * @design	  :like OS mode, there is many heaps.we need to use heap pool manage
 *            :them! The heap pool of user mode initalized by size 0 bytes.
 *            :we will try our best to allocate memory in current process.
 *            :But there's still some specials, like create the default thread
 *            :for a new process.It needs use indicate the FLPT of new process,
 *            :because we can;t allocate memory in a process memory space for
 *            :ather process's threads!
 */

#ifndef _K_MM_USR_HEAP_C_
#define _K_MM_USR_HEAP_C_

#include "k_mem_sysmmu.h"
#include "k_mm_heap.h"
#include "mem_basedefine.h"
#include "../task/kernel_task_def.h"
#include "../k_common_asm.h"


extern K_PProcess      g_p_cur_process;		// current running process
extern K_PThread       g_p_cur_thread;		// current running thread

int k_mm_usr_heap_create(unsigned int size);

/**
 * @BRIEF	: allocate memory from heap
 * @PARAM	: size 		size of memory in byte
 * @RETRUN	: memory pointer
 * @WARNING	: virtual memory
 */
void *k_mm_usr_heap_allocate(unsigned int size)
{
    K_HEAPPOOL      *p_proc_heappool = &g_p_cur_process->proc_heappool;
    K_PHEAP         cur_heap;
    K_PHEAPBLOCK    cur_block;
    K_PHEAPBLOCK    hb_pre = 0;

    if (size == 0)
    {
        return 0;
    }

    /* size should be added heap block header size */
    size += K_HEAPBLOCK_HEADERSIZE;
    do
    {
	    cur_heap = p_proc_heappool->hp_firstheap;

		K_MEM_DISABLE_INTERRUPT;    // disable interrupt
        for (;cur_heap != 0;cur_heap = cur_heap->heap_next)
        {
            if (size >(cur_heap->heap_totalsize - cur_heap->heap_usedsize))
            {
                // we have find next.
                continue;
            }

            // we can try to find deeply with heap block
            cur_block = cur_heap->heap_firsthb;
            for (;cur_block != 0;cur_block = cur_block->hb_next)
            {
                if (cur_block->hb_mflag != K_HEAPBLOCK_HEADERFLAG_FREE)
                {
                    // allcated by other thread
                    continue;
                }

                if (cur_block->hb_size <= size && size <= (cur_block->hb_size + K_HEAPBLOCK_HEADERSIZE))
                {
                    if (hb_pre == 0)
                    {
                        // allocate the first block
                        cur_heap->heap_firsthb = cur_block->hb_next;
                    }
                    else
                    {
                        hb_pre = cur_block->hb_next;
                    }
                    cur_block->hb_mflag = K_HEAPBLOCK_HEADERFLAG_USING;
                    K_MEM_ENABLE_INTERRUPT;
                    return (void *)((unsigned int)cur_block + K_HEAPBLOCK_HEADERSIZE);
                }
                if ((cur_block->hb_size + K_HEAPBLOCK_HEADERSIZE)< size)
                {
                    // we need to find next
                    hb_pre = cur_block;
                    continue;
                }
                if (cur_block->hb_size > size)
                {
                    // create new block from bottom of current block
                    K_PHEAPBLOCK p_new = (K_PHEAPBLOCK)((unsigned int)cur_block + K_HEAPBLOCK_HEADERSIZE + cur_block->hb_size - size);
                    cur_block->hb_size -= size;

                    K_MEM_ENABLE_INTERRUPT;
                    p_new->hb_next = 0;
                    p_new->hb_parent = cur_heap;
                    p_new->hb_mflag = K_HEAPBLOCK_HEADERFLAG_USING;
                    p_new->hb_size = size - K_HEAPBLOCK_HEADERSIZE;

                    return (void *)((unsigned int)p_new + K_HEAPBLOCK_HEADERSIZE);
                }
            }
            // in current heap, we can't find memory!
            // we continue next heap
        }

        K_MEM_ENABLE_INTERRUPT; // we enable interrupt now.may be we never loop
        // in all heaps we can not find memory..
        // we try to create new heap for current process
    }
    while (k_mm_usr_heap_create(size));
	return 0;
}

/**
 * @BRIEF	: create new heap in usr space and add it to heap pool of process
 * @PARAM	: size          heap size. it will be bouned to 4KB auto.
 * @RETRUN	: 0 failed, 1 success.
 * @WARNING	: virtual memory
*/
int k_mm_usr_heap_create(unsigned int size)
{
    // we need to add heap header and default block header
	K_PHEAP p_heap_new;
	K_PHEAPBLOCK p_block;

    size += K_HEAP_HEADERSIZE + K_HEAPBLOCK_HEADERSIZE;
    K_BOUND_4KB(size);

    // now we allocate new memory

    size = size >> 12;          // pages

    p_heap_new = (K_PHEAP)k_mem_mmu_allocate(K_MEM_PHY_NORMAL, size);

    if (p_heap_new == 0)
    {
        // we failed
        return 0;
    }

    p_heap_new->heap_totalsize = size << 12;
    p_heap_new->heap_usedsize  = K_HEAP_HEADERSIZE + K_HEAPBLOCK_HEADERSIZE;
    p_heap_new->heap_gcfreq = 0;

    // create default heap block K_HEAP_HEADERSIZE
    p_block = (K_PHEAPBLOCK)((unsigned int)p_heap_new + K_HEAP_HEADERSIZE);
    p_block->hb_mflag = K_HEAPBLOCK_HEADERFLAG_FREE;
    p_block->hb_next  = 0;
    p_block->hb_parent = p_heap_new;
    p_block->hb_size = (size << 12) - K_HEAP_HEADERSIZE - K_HEAPBLOCK_HEADERSIZE;

    p_heap_new->heap_firsthb = p_block;

    /** add this to heap pool of current process  .. we need to disable interrupt **/
    K_MEM_DISABLE_INTERRUPT;
    p_heap_new->heap_next = g_p_cur_process->proc_heappool.hp_firstheap;
    g_p_cur_process->proc_heappool.hp_firstheap = p_heap_new;
    g_p_cur_process->proc_heappool.hp_heapcount++;
    g_p_cur_process->proc_heappool.hpool_size += size << 12;
    K_MEM_ENABLE_INTERRUPT;
    return 1;
}

/**
 * @BRIEF	: Free memory to heap of process
 * @PARAM	: mem_addr		address of memory to free
 * @RETRUN	: void
 * @WARNING	: virtual memory
 */
void k_mm_usr_heap_free(void *mem_addr)
{
    K_PHEAPBLOCK block = (K_PHEAPBLOCK)((unsigned int)mem_addr - K_HEAPBLOCK_HEADERSIZE);

	// we should check that: user mode applications can't free OS virtual address.
	// OS virtual address only can be freed by OS program or driver!
	if(mem_addr <= (K_SYS_PROCESS_HEAPSTACKSTART_START << 20))
	{
		// the applicaton must be hack, but we check here ^_^
		return;
	}

    // check whether dirty
    if (block->hb_mflag != K_HEAPBLOCK_HEADERFLAG_USING)
    {
        // .... shit....
        // I don't know what should do now!
    }
    block->hb_mflag = K_HEAPBLOCK_HEADERFLAG_FREE;	// set flag.
    // we add current block to heap list which it is belonging to
    K_MEM_DISABLE_INTERRUPT;
    block->hb_next = ((K_PHEAP)block->hb_parent)->heap_firsthb;
    ((K_PHEAP)block->hb_parent)->heap_gcfreq++;
    ((K_PHEAP)block->hb_parent)->heap_firsthb = block;
    ((K_PHEAP)block->hb_parent)->heap_usedsize -= block->hb_size;

    // check whether we need to GC current heap
//  if (((K_PHEAP)block->hb_parent)->heap_gcfreq > K_SYS_OS_HEAP_GCFREQ)
    {
        k_mm_heap_gc((K_PHEAP)block->hb_parent);
    }

    // we always try to free heap in usr mode.
    k_mm_heap_freeheap(&g_p_cur_process->proc_heappool);
    K_MEM_ENABLE_INTERRUPT;
}

#endif /* _K_MM_USR_HEAP_C_ */

